/*
 ------------------------------------------------------------------------------
 Copyright (C) 2013 Eternal Games.

 This file is part of the EternalTech source code.

 The EternalTech source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The EternalTech source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the EternalTech source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// sg_commands.cpp - operator commands
//


#include "sg_local.h"


/*
 ==============================================================================

 IP FILTERING

 You can add or remove addresses from the filter list with:

 addIP <ip>
 removeIP <ip>

 The IP address is specified in dot format, and you can use a filter to match
 any value so you can specify an entire class C network with, for example,
 "addIP 192.246.40.*".

 The removeIP command will only remove an IP address specified exactly the same
 way. You cannot addIP a subnet, then removeIP a single host.

 The listIP command prints the current list of filters.

 If sg_filterBan is enabled then IP addresses matching the current list will be
 prohibited from entering the game.

 ==============================================================================
*/

#define IP_FILTERS_FILE					"banIPs.cfg"


/*
 ==================
 SG_ParseBanIPs
 ==================
*/
void SG_ParseBanIPs (){

	script_t	*script;
	token_t		token;
	int			count = 0;

	script = glqParser->LoadScriptFile(IP_FILTERS_FILE);
	if (!script)
		return;

	glqParser->SetScriptFlags(script, SF_NOWARNINGS | SF_NOERRORS | SF_PARSEPRIMITIVES);

	while (1){
		if (!glqParser->ReadToken(script, &token))
			break;

		if (count == MAX_IP_FILTERS){
			G_Printf("Too many IPs in %s\n", IP_FILTERS_FILE);
			break;
		}

		level.ipFilters[count++] = glqMemory->DupString(token.string, TAG_SERVER);
	}

	glqParser->FreeScript(script);
}

/*
 ==================
 SG_UpdateBanIPs
 ==================
*/
static void SG_UpdateBanIPs (){

	fileHandle_t	f;
	int				i, count = 0;

	for (i = 0; i < MAX_IP_FILTERS; i++){
		if (!level.ipFilters[i])
			continue;

		count++;
	}

	if (!count){
		G_Printf("Removing %s...\n", IP_FILTERS_FILE);

		glqFileSystem->RemoveFile(IP_FILTERS_FILE);
		return;
	}

	G_Printf("Writing %s...\n", IP_FILTERS_FILE);

	glqFileSystem->OpenFile(IP_FILTERS_FILE, FS_WRITE, &f);
	if (!f){
		G_Printf("Couldn't write %s\n", IP_FILTERS_FILE);
		return;
	}

	for (i = 0; i < MAX_IP_FILTERS; i++){
		if (!level.ipFilters[i])
			continue;

		glqFileSystem->Printf(f, "%s ", level.ipFilters[i]);
	}

	glqFileSystem->CloseFile(f);
}

/*
 ==================
 SG_FilterIP
 ==================
*/
bool SG_FilterIP (const char *ip){

	int		i;

	if (!sg_filterBan->integerValue)
		return false;

	if (!Str_ICompare(ip, "localhost"))
		return false;

	for (i = 0; i < MAX_IP_FILTERS; i++){
		if (!level.ipFilters[i])
			continue;

		if (Str_MatchFilter(ip, level.ipFilters[i], false))
			return true;
	}

	return false;
}

/*
 ==================
 SG_AddIP_f
 ==================
*/
static void SG_AddIP_f (){

	int		i;

	if (glqCmdSystem->Argc() != 2){
		G_Printf("Usage: addIP <ip>\n");
		return;
	}

	for (i = 0; i < MAX_IP_FILTERS; i++){
		if (!level.ipFilters[i])
			break;
	}

	if (i == MAX_IP_FILTERS){
		G_Printf("IP filter list is full!\n");
		return;
	}

	level.ipFilters[i] = glqMemory->DupString(glqCmdSystem->Argv(1), TAG_SERVER);

	SG_UpdateBanIPs();
}

/*
 ==================
 SG_RemoveIP_f
 ==================
*/
static void SG_RemoveIP_f (){

	int		i;

	if (glqCmdSystem->Argc() != 2){
		G_Printf("Usage: removeIP <ip>\n");
		return;
	}

	for (i = 0; i < MAX_IP_FILTERS; i++){
		if (!level.ipFilters[i])
			continue;

		if (!Str_Compare(level.ipFilters[i], glqCmdSystem->Argv(1))){
			glqMemory->Free(level.ipFilters[i]);
			level.ipFilters[i] = NULL;
		}
	}

	SG_UpdateBanIPs();
}

/*
 ==================
 SG_ListIP_f
 ==================
*/
static void SG_ListIP_f (){

	int		i;

	G_Printf("Filtered IP addresses:\n");

	for (i = 0; i < MAX_IP_FILTERS; i++){
		if (!level.ipFilters[i])
			continue;

		G_Printf("%s\n", level.ipFilters[i]);
	}
}


/*
 ==============================================================================

 TODO

 ==============================================================================
*/


/*
 ==================
 SG_ArgCompletion_EntityName
 ==================
*/
static void SG_ArgCompletion_EntityName (void (*callback)(const char *string)){

	etEntity	*entity;
	int			i;

	for (i = 0, entity = level.entities; i < level.numEntities; i++, entity++){
		if (!entity->inUse)
			continue;

		callback(entity->name);
	}
}

/*
 ==================
 SG_ListEntities_f
 ==================
*/
static void SG_ListEntities_f (){

	etEntity	*entity;
	const char	*type;
	int			count = 0;
	int			entBytes = 0, dictBytes = 0;
	int			i;

	entBytes += glqMemory->Size(level.entities);

	G_Printf("\n");
	G_Printf("      -type----- -class------------------- -name-----------\n");

	for (i = 0, entity = level.entities; i < level.numEntities; i++, entity++){
		if (!entity->inUse)
			continue;

		count++;

		entBytes += entity->Allocated();
		dictBytes += entity->spawnDict.Size();

		switch (entity->state.type){
		case ET_GENERAL:
			type = "GENERAL";
			break;
		case ET_MOVER:
			type = "MOVER";
			break;
		case ET_BEAM:
			type = "BEAM";
			break;
		case ET_LIGHT:
			type = "LIGHT";
			break;
		case ET_SPEAKER:
			type = "SPEAKER";
			break;
		case ET_PLAYER:
			type = "PLAYER";
			break;
		case ET_ITEM:
			type = "ITEM";
			break;
		default:
			type = "UNKNOWN";
		}

		G_Printf("%4i: %-10s %-25s %s\n", i, type, entity->className, entity->name);
	}

	G_Printf("-----------------------------------------------------------\n");
	G_Printf("%i total entities\n", count);
	G_Printf("%.2f MB of entity data\n", SIZE_MB_FLOAT(entBytes));
	G_Printf("%.2f MB of dictionary data\n", SIZE_MB_FLOAT(dictBytes));
	G_Printf("\n");
}

/*
 ==================
 SG_ListKeyValues_f
 ==================
*/
static void SG_ListKeyValues_f (){

	etEntity	*entity;

	if (glqCmdSystem->Argc() != 2){
		G_Printf("Usage: listKeyValues <entityName>\n");
		return;
	}

	entity = SG_FindEntity(glqCmdSystem->Argv(1));
	if (!entity){
		G_Printf("Entity '%s' not found\n", glqCmdSystem->Argv(1));
		return;
	}

	entity->spawnDict.ListKeyValues();
}

/*
 ==================
 SG_ListConfigStrings_f
 ==================
*/
static void SG_ListConfigStrings_f (){

	int		i;

	for (i = 0; i < MAX_CONFIG_STRINGS; i++){
		if (!level.configStrings[i])
			continue;

		G_Printf("%4i: %s\n", i, level.configStrings[i]);
	}
}

/*
 ==================
 SG_Spawn_f
 ==================
*/
static void SG_Spawn_f (){

	etDict		dict;
	glqVec3		origin;
	const char	*key, *value;
	int			i;

	if (glqCmdSystem->Argc() & 1){
		G_Printf("Usage: spawn <className> [key/value pairs]\n");
		return;
	}

	if (!glqCVarSystem->AllowCheats()){
		G_Printf("You must enable cheats to use this command\n");
		return;
	}

	// Put it in front of the view
	if (!level.localClient){
		G_Printf("You must be connected to the server to use this command\n");
		return;
	}

	origin = level.localClient->GetViewOrigin() + level.localClient->GetViewAngles().ToForward() * 100.0f;
	origin.Snap();

	// Build the dictionary of key/value pairs
	dict.SetString("className", glqCmdSystem->Argv(1));
	dict.SetVec3("origin", origin);

	for (i = 2; i < glqCmdSystem->Argc() - 1; i += 2){
		key = glqCmdSystem->Argv(i);
		value = glqCmdSystem->Argv(i + 1);

		dict.SetString(key, value);
	}

	// Spawn the entity
	SG_SpawnEntity(dict);
}

/*
 ==================
 SG_Show_f
 ==================
*/
static void SG_Show_f (){

	etEntity	*entity;

	if (glqCmdSystem->Argc() != 2){
		G_Printf("Usage: show <entityName>\n");
		return;
	}

	if (!glqCVarSystem->AllowCheats()){
		G_Printf("You must enable cheats to use this command\n");
		return;
	}

	entity = SG_FindEntity(glqCmdSystem->Argv(1));
	if (!entity){
		G_Printf("Entity '%s' not found\n", glqCmdSystem->Argv(1));
		return;
	}

	// Show the entity
	entity->Show();
}

/*
 ==================
 SG_Hide_f
 ==================
*/
static void SG_Hide_f (){

	etEntity	*entity;

	if (glqCmdSystem->Argc() != 2){
		G_Printf("Usage: hide <entityName>\n");
		return;
	}

	if (!glqCVarSystem->AllowCheats()){
		G_Printf("You must enable cheats to use this command\n");
		return;
	}

	entity = SG_FindEntity(glqCmdSystem->Argv(1));
	if (!entity){
		G_Printf("Entity '%s' not found\n", glqCmdSystem->Argv(1));
		return;
	}

	// Hide the entity
	entity->Hide();
}

/*
 ==================
 SG_Teleport_f
 ==================
*/
static void SG_Teleport_f (){

	etEntity	*entity;
	glqVec3		origin;
	glqAngles	angles;

	if (glqCmdSystem->Argc() != 5 && glqCmdSystem->Argc() != 6){
		G_Printf("Usage: teleport <entityName> <x> <y> <z> [yaw]\n");
		return;
	}

	if (!glqCVarSystem->AllowCheats()){
		G_Printf("You must enable cheats to use this command\n");
		return;
	}

	entity = SG_FindEntity(glqCmdSystem->Argv(1));
	if (!entity){
		G_Printf("Entity '%s' not found\n", glqCmdSystem->Argv(1));
		return;
	}

	origin.Set(Str_ToFloat(glqCmdSystem->Argv(2)), Str_ToFloat(glqCmdSystem->Argv(3)), Str_ToFloat(glqCmdSystem->Argv(4)));

	if (glqCmdSystem->Argc() == 5)
		angles.Clear();
	else
		angles.Set(0.0f, Str_ToFloat(glqCmdSystem->Argv(5)), 0.0f);

	// Teleport the entity
	entity->Teleport(origin, angles);
}

/*
 ==================
 SG_Remove_f
 ==================
*/
static void SG_Remove_f (){

	etEntity	*entity;

	if (glqCmdSystem->Argc() != 2){
		G_Printf("Usage: remove <entityName>\n");
		return;
	}

	if (!glqCVarSystem->AllowCheats()){
		G_Printf("You must enable cheats to use this command\n");
		return;
	}

	entity = SG_FindEntity(glqCmdSystem->Argv(1));
	if (!entity){
		G_Printf("Entity '%s' not found\n", glqCmdSystem->Argv(1));
		return;
	}

	// Remove the entity
	SG_FreeEntity(entity);
}

/*
 ==================
 SG_ConSay_f
 ==================
*/
void SG_ConSay_f (){

	etClient	*client;
	char		string[MAX_STRING_LENGTH];
	int			i;

	if (level.localClient == NULL)
		Str_SPrintf(string, sizeof(string), "Server: %s", glqCmdSystem->Args(1, -1, false));
	else
		Str_SPrintf(string, sizeof(string), "%s" S_COLOR_DEFAULT ": %s", level.localClient->playerName, glqCmdSystem->Args(1, -1, false));

	// Print to system console
	if (com_dedicated->integerValue)
		G_Printf("%s\n", string);

	// Broadcast to all active clients
	for (i = 0, client = level.clients; i < MAX_CLIENTS; i++, client++){
		if (client->connected != CON_CONNECTED)
			continue;	// Not fully in game yet

		glqServer->AddReliableCommand(client->number, "chat %s", string);
	}
}


// ============================================================================


/*
 ==================
 SG_AddCommands
 ==================
*/
void SG_AddCommands (){

	glqCmdSystem->AddCommand("addIP", SG_AddIP_f, "Adds an IP address to the filter list", NULL);
	glqCmdSystem->AddCommand("removeIP", SG_RemoveIP_f, "Removes an IP address from the filter list", NULL);
	glqCmdSystem->AddCommand("listIP", SG_ListIP_f, "Lists filtered IP addresses", NULL);
	glqCmdSystem->AddCommand("listEntities", SG_ListEntities_f, "Lists entities", NULL);
	glqCmdSystem->AddCommand("listKeyValues", SG_ListKeyValues_f, "Lists the key/value pairs of an entity", SG_ArgCompletion_EntityName);
	glqCmdSystem->AddCommand("listConfigStrings", SG_ListConfigStrings_f, "Lists config strings", NULL);
	glqCmdSystem->AddCommand("spawn", SG_Spawn_f, "Spawns an entity", NULL);
	glqCmdSystem->AddCommand("show", SG_Show_f, "Shows an entity", SG_ArgCompletion_EntityName);
	glqCmdSystem->AddCommand("hide", SG_Hide_f, "Hides an entity", SG_ArgCompletion_EntityName);	
	glqCmdSystem->AddCommand("teleport", SG_Teleport_f, "Teleports an entity", SG_ArgCompletion_EntityName);
	glqCmdSystem->AddCommand("remove", SG_Remove_f, "Removes an entity", SG_ArgCompletion_EntityName);
	glqCmdSystem->AddCommand("conSay", SG_ConSay_f, "Server text chat", NULL);
}

/*
 ==================
 SG_RemoveCommands
 ==================
*/
void SG_RemoveCommands (){

	glqCmdSystem->RemoveCommand("addIP");
	glqCmdSystem->RemoveCommand("removeIP");
	glqCmdSystem->RemoveCommand("listIP");
	glqCmdSystem->RemoveCommand("listEntities");
	glqCmdSystem->RemoveCommand("listKeyValues");
	glqCmdSystem->RemoveCommand("listConfigStrings");
	glqCmdSystem->RemoveCommand("spawn");
	glqCmdSystem->RemoveCommand("show");
	glqCmdSystem->RemoveCommand("hide");
	glqCmdSystem->RemoveCommand("teleport");
	glqCmdSystem->RemoveCommand("remove");
	glqCmdSystem->RemoveCommand("conSay");
}